WW WW WW PPPPPPPP JJ WW WW WW PP PP JJ WW WWWW WW PP PP JJ WW WW WW WW PPPPPPPP JJ WW WW WW WW PP JJ JJ WWWW WWWW PP JJ JJ WW WW PP JJJJJ ---------------------------------------------------------------- The Windows Programmer's Journal Volume 01 Copyright 1993 by Peter J. Davis Number 02 and Mike Wallace Feb 93 ---------------------------------------------------------------- A monthly forum for novice-advanced programmers to share ideas and concepts about programming in the Windows (tm) environment. You can get in touch with the editor via Internet or Bitnet at: HJ647C at GWUVM.BITNET or HJ647C at GWUVM.GWU.EDU CompuServe: 71141,2071 or you can send paper mail to: Windows Programmer's Journal 9436 Mirror Pond Dr. Fairfax, Va. 22032 The two GWUVM IDs are Pete's and CompuServe is Mike's. We can also be reached by phone at: (703) 503-3165. Microsoft, MS-DOS, Microsoft Windows, Windows NT, Windows for Workgroups, Windows for Pen Computing, Win32, and Win32S are registered trademarks of Microsoft Corporation. Turbo Pascal for Windows, Turbo C++ for Windows, and Borland C++ for Windows are registered trademarks of Borland International. WordPerfect is a registered trademark of WordPerfect Corporation. WPJ is available from the WINSDK, WINADV and MSWIN32 forums on CompuServe, and the IBMPC, WINDOWS and BORLAND forums on GEnie. On Internet, it's available on WSMR-SIMTEL20.ARMY.MIL and FTP.CICA.INDIANA.EDU. We upload it by the 1st of each month and is usually available by the 3rd or 4th, depending on when the sysops receive it. The Windows Programmer's Journal takes no responsibility for the content of the text within this document. All text is the property and responsibility of the individual authors. The Windows Programmer's Journal is solely a vehicle for allowing articles to be collected and distributed in a common and easy to share form. No part of the Windows Programmer's Journal may be re-published or duplicated in part or whole, except in the complete and unmodified form of the Windows Programmer's Journal, without the express written permission of each individual author. The Windows Programmer's Journal may not be sold for profit without the express written permission of the Editor, Peter J. Davis, and only then after he has obtained permission from the individual authors. Table of Contents Subject Page Author(s) ----------------------------------------------------------------- WPJ.INI ....................................... 3 Pete Davis Letters ....................................... 5 Readers Install Program Part II ....................... 7 Pete Davis Programming a Drag&Drop Server ................ 9 Andreas Furrer C++ Beginner's Column ......................... 12 Mike Wallace Beginner's Corner (C) ......................... 14 Pete Davis & Mike Wallace Using LZExpand Library ........................ 18 Alex Fedorov Implementing a Linked List - Revisited ........ 21 Mike Wallace An Introductory Look at DLLs and Make Files ... 22 Rod Haxton The Windows Help Magician ..................... 29 Jim Youngman Last Page .................................... 30 Mike Wallace Getting in Touch with Us ..................... 31 Pete & Mike Windows Programmer's Journal Staff: Publishers ......................... Pete Davis and Mike Wallace Editor-in-Chief .................... Pete Davis Managing Editor .................... Mike Wallace Contributing Writer ................ Andreas Furrer Contributing Writer ................ Alex Federov Contributing Writer ................ Rod Haxton Contributing Writer ................ Jim Youngman WPJ.INI By Pete Davis Well, welcome to the second issue of the Windows Programmer's Journal. I don't really know what to say. We've been totally blown away by the response we've been getting. I'm writing this on January 15th and as of today, on Compuserve and GEnie (the only two places we can really see how many issues are being downloaded) we've counted 873 downloads. That number goes up every day and it doesn't include all of the other places that the Windows Programmer's Journal is available. We've gotten letters from a lot of you with good and bad things to say. (When I say bad, I really mean critical.) Criticism is fine and we're even going to publish some of it in this issue. We've also received a terrific response from Andrew Schulman, whose book we reviewed in the last issue. We'll show an excerpt from that and other letters in the letters column. One of the criticisms that was most discussed was the Linked List article written by Mike. We should be receiving an article from Peter Shroesbree for the next issue, showing an alternate way of doing it. We'll also have a third article, showing yet another way of doing this in next month's issue, done by Rod Haxton, who's writing an article in this issue. Since this has become a bit of a hot topic, we're going to do a short article in next month's issue about the advantages and disadvantages of each of these methods. We'd also like to welcome David Campbell. In March, he'll be taking over the beginner's column. David has a good bit of experience in Windows programming and is quite a hacker. He has shareware and commercial software on the market, so his experience will be valuable to all of us. Speaking of experience, I suppose it's time to kind of spill the beans about Mike and I. So as not to confuse anyone, neither Mike nor I have a lot of experience programming Windows. We are not authorities on the subject, but we don't think that means we don't know a few things that we can share. We're both learning a lot all the time. The linked list article is an example. All I'm trying to say is, don't say, "Well, the Windows Programmer's Journal says this, so it must be true." Not that you would, but we all can make mistakes and we're not always going to be right. The good thing is that there are obviously a lot of you out there reading this and with your help, we can make corrections. We have another article from Andreas Furrer this month. Last month he wrote an article about programming a Drag & Drop client in Windows 3.1. This month he's going to talk about writing a Drag & Drop server program. We've also got an article by Alex Federov of Moscow, Russia on using the LZEXPAND.DLL. Just got my sample issue of Windows Tech Journal. Ahem... How do you spell cancel? Nah, it ain't that bad, just not really my type of magazine, I guess. Had an offer for Windows NT Developer. 12 issues for a mere $129. No thanks, I'll buy the book. Sorry, I digress. Geez, let's see, I'm going to be writing the second part to the install program. I'm basically covering a lot of the stuff Alex Federov is covering. His sample, however, is written in Turbo Pascal for Windows. I was supposed to do an article on printing, this month, but I've been really busy, so I'm going to have to put it off until the next issue. I'll share a really wonderful experience with you about my trials in learning to print also. Ah, and I almost forgot. We're going to do a reader poll next month. Here's the question. What format would you like to see WPJ in? We've had suggestions for a lot of different formats. Mike is leaning in the Windows Write direction. That's ok, but it doesn't quite have the power of a real word processor, which is a pain when putting the whole thing together. Dave Campbell suggested Windows WINHELP format. I've seen a sample, and I have to say, it's pretty damn impressive. We've also had suggestions for Postscript and TeX. Now, the problem with Postscript, as I see it, is it's BIG. That means it'll take longer for you to download. If you're getting it off Compuserve or GEnie, or some other pay system, it costs you. As far as TeX, I don't know how popular it is. Personally I've never used it and don't know anyone who has (though I've heard good things about it), so unless we get a huge outcry for the TeX format, I don't think we'll go that way. That leaves the regular text format that you're getting it in now, Write format, or WinHelp format. Now, before you cast your votes, we will be distributing the March issue in all three of those formats. After March, tell us what you think. I just wanted to give everyone a heads-up on that. One final note: We mentioned our BBS in the last issue. Well, we had it up for about 3 days and before anyone had a chance to call, the hard drive got wiped out. I've been trying to get the thing back together, but the hard drive is really getting unreliable. What it looks like I'm going to have to do is get rid of it and replace it. I currently have two 65 meg Seagates which have done their time. The main one (C: drive) is just getting a little too flakey, so we're going to toss it and probably throw in a 600 meg hard drive that we saw a good deal on. (We have two machines, so we're going to network them so that Mike can use the 600 meg on his machine too.) Anyway, we don't know if we'll have it up this month. If we don't, it'll be next month. Sorry for the problems there. I'd like to thank everyone who's reading the magazine and sending in their comments and suggestions. It helps us to do a better job and it helps you get a better magazine. We really appreciate your comments and we'd like you to keep them coming. Also, as always, please, please, send us your articles. We want them!!!! By the way, it's now January 31 and the number of downloads on GEnie and Compuserve alone are about 1200, total. Sorry, just about broke my arm patting myself on the back there. 'Scuse me while I pump up our egos a bit. We'll try not to do that too often. And remember, if you read it in the Programmer's Journal, it might be right! _Pete Davis Letters Date: 04-Jan-93 13:48 EST From: Andrew Schulman [76320,302] Subj: Windows Programmer's Journal Thanks very much for sending the magazine. Of course, I read the review of UndocWin very carefully, and read the rest pretty carefully. I guess my main question is, Why the heck are you guys doing this? You both write well. The material is interesting and entertaining. So why are you giving it away for free? Doesn't make any sense to me. You could be making money from your writing, though not necessarily with your own magazine. But clearly you could write for MSJ, WDDJ, or Dr. Dobb's (I won't mention that other magazine that covers Windows programming...). So, like, why give the stuff away? Thanks for the kind review of UndocWin. If you are looking for more stuff on how Windows operates, I think you'll be pleased with the book "Windows Internals" that Matt Pietrek is just finishing up. It will appear in the series of books I am editing for Addison-Wesley, probably in March or April. Most of the book is typeset already. It presents detailed pseudocode for many of the key Windows API functions. For example, if you want to know what CreateWindow or RegisterClass or GetMessage or ShowWindow or GlobalAlloc actually does, in sometimes painful detail, this is it. UndocNT? Well, that's an interesting question. Microsoft has asked me that question too. As you know, the entire NT API (as opposed to the Win32 API) is right now undocumented. Helen Custer's book "Inside Windows NT" does a good job of showing in a conceptual-overview sort of way how NT works, but as she herself says in the preface the book's goal is to how "exactly how NT sort of works" Exactly sort of! A brief examination of the NT process viewer, PVIEW.EXE, shows that there is some dynamite stuff at the NT API layer that currently isn't available via Win32. (Ray Duncan was who put me on to looking at PVIEW; I looked at it first with Microsoft's COFF -DUMP, and have since been modifying my Windows Source product to disassemble PE files.) So there's a lot of interesting stuff in NT. And Microsoft actually seems to look _favorably_ on an Undocumented NT book. This way, they don't have to document it! There's just one problem: to do a book like UndocDOS or UndocWin or UndocNT takes a long time. Basically, it's not worth doing such a book unless you're going to sell a lot of copies. And I do not think that NT anything is going to sell a lot of copies for a few years. To me, the whole thing is reminiscent of OS/2 in a lot of ways. (In a lots of ways, it's not reminiscent of OS/2 of course: Microsoft has clearly learned a lot of lessons, but I think it's also repeating some of the same mistakes.) Me, I'm putting my money on Win32s rather than NT. I told one of the guys at Microsoft that I would start working on it after they sold a million copies of NT. I'm not holding my breath. In looking over this letter, I realize that the phrases "money" and "sell" keep appearing. This must say something about me. :-) Regards, Andrew [Thanks for the kind review of WPJ, Andrew, and the tip on "Windows Internals" - I look forward to reading it. -Mike] Date: 10-Jan-93 03:22 EST From: Alex Fedorov [72400,274] Subj: WPJ Mike-- Here is Alex Fedorov from Moscow, Russia. Yesterday I downloaded the 1st issue of WPJ. This is great! I've a whole set of Peter's previous magazine - Pascal News Letter. I liked it. I'm working as an editor for our "Computer Press" magazine - the most popular computer magazine here. Before that I've worked as tech support person for Borland Pascal for one of its distributors here. Reading WPJ.INI section, I've realized that you are looking for authors. I would like to offer a set of articles, dedicated to changes in Windows 3.1 - new DLLs, concepts and APIs from Turbo Pascal for Windows. These articles were prepared for publication here and can be translated in a short time. The articles cover new kernel API functions, TrueType fonts, OLE/DDEML, COMMDLG, Drag and Drop, VER and TOOLHELP libraries. Beside the texts, there is plenty of examples, which can be used like small utilities. Please let me know if this is interesting for beginner/intermediate section of WPJ. Also, I've plenty of hacks for advanced users. Let me know if you need more information. Sincerely, Alex [Glad you liked the 1st issue, Alex. Hope the rest go over as well. We've included your first article in this issue and plan to include more in future issues. -Mike] Install Program Part II: File Decompression By Pete Davis Well, I don't know where my head's been, but I think I found it. Last time I was discussing how much I was dreading coming up with a decompression algorithm and it occurred to me that I don't need to. The LZEXPAND.DLL has all the routines we need for decompression. I was still in the Windows 3.0 mindset. When I did my first install program the LZEXPAND.DLL wasn't available to me, so I didn't even consider it last month. Several of you mentioned this to me also. In retrospect, it seems so obvious. Oh well.... You should also read Alex Fedorov's article on using LZEXPAND under Turbo Pascal in this issue. Ok, so for those of you that don't know, LZEXPAND.DLL is a set of file decompression routines supplied with Windows 3.1. Because I want to maintain 3.0 compatibility (a bad habit I pick up from work, I guess) I am supplying the LZEXPAND.DLL file with this issue. This article isn't going to be too long because, thanks to Microsoft, the LZEXPAND.DLL takes all the work out of file decompression. They make it as easy as opening, reading, and closing a file. One feature we're going to have in our program is two of those nifty little progress bars that shows us how far we are through our installation. One is going to show progress for the entire installation. The other is going to handle progress for the current file. This is where we run into a bit of a problem. In the past I used the .ZIP format and that kept the total uncompressed file size internally so I could figure out how many bytes the file was going to be uncompressed at run-time. I have been unable to find a way to do that with the LZEXPAND.DLL so we're going to have that in our SETUP.INF file. Let me run off on a little tangent here and explain the SETUP.INF. The SETUP.INF is going to be the file that makes our installation program generic. The SETUP.INF file is going to keep information like the name of the Application group, the default destination directory, whether certain files will go into sub-directories within the application directory, how large each file is un-compressed, what the compressed filename is on the installation diskette and so on and so on. We're also going to break the rules a bit. I mentioned earlier the trouble that Mike got into with his article about doing Global linked lists. Well, we're going to use his 'bad' example and in this case, I believe I can justify it. First of all, an install program has, in this programmer's mind, every right to hog up the CPU. (Just try to run another program while the floppy drive's busy anyway.) Second, it's going to be playing around with Program Manager, and when that's going on, you don't want the user clicking around everywhere and screwing up the installation, so, we're going to hog up the CPU. That means that whatever global memory is there is ours for the taking and gosh darnit, we're going to take what we want! So, bear with me on our nasty little global linked list. (That will be in next months issue). Well, that little side-track got a little longer than I expected. Anyway, back to LZEXPAND. So, we know the uncompressed file size from our SETUP.INF. What's left is to get our current progress. That's pretty easy. The way it works is that when you read from a compressed file, you have to allocate the buffer. That means that if the buffer is, say, 20k, then every time 20k of data is decompressed, we have to write out the data in the buffer to our uncompressed file on the destination disk. So, all we have to do is update our progress bar by 20k and whatever percent of the installation that is. The formulas are really simple. The commands we're going to be concerned about are: LZInit, GetExpandedName, LZRead, and LZClose. The LZInit function essentially allocates memory for the decompression algorithm and initializes some data that the algorithm uses. The prototype is: HFILE LZInit(HFILE SrcFile) SrcFile is the file handle received from a regular OpenFile function call. We use this handle only for the LZInit, but we keep the file open until the end of our decompression. If the return value from LZInit is the same as SrcFile, then that means our file isn't compressed. If the return value is greater than 0, then it is a special file handle for our compressed file. If the return value is less than 0, then we have an error. After we do the LZInit, we have to do a GetExpandedName to get the filename of our file as it was prior to being compressed. We'll use this filename when we open the output file to write the uncompressed version of the file. The prototype for the GetExpandedName is: int GetExpandedName(LPCSTR lpszSource, LPSTR lpszBuffer); lpszSource is a pointer to the string that has the filename of the compressed file. lpszBuffer will have the name of the file prior to it's compression. This is the filename that we will use when writing the file back. The return value is TRUE if successful. After that, of course, is the LZRead which, again (isn't this just the most bizarre thing) is a lot like the _lread function. It's prototype: int LZRead(HFILE hf, void FAR* lpvBuf, int cb); hf is the file handle we returned from the LZInit. lpvBuf is our buffer to hold the data we read. cb is the number of bytes read from the file. This is the number we'll use to write the data out to our output file. Last, but not least, the LZClose. It's simply: void LZClose(HFILE hf); where hf is the file handle to close. That's about all we need. It's pretty simple. There are other LZ commands and maybe at some point I'll have a discussion of the entire library of commands. At this point, though, it's confession time. I must admit I have no code to go with this article this month. Fear not, it will be in next months issue. I'm hoping to wrap up the entire thing in next months issue, and with several other people helping out in next months issue, that just might be possible. Anyway, until then, mer i beaucoup et au r voir. (Just trying to be a little international there.) Programming a Drag&Drop Server for Windows 3.1 with TPW by Andreas Furrer Last issue I explained how to implement a client for Drag&Drop with File Manager. This was easy because it is well documented by Microsoft. But now, what if we want to be the server for Drag&Drop (e.g. if you want to program your own file manager)? There is no documentation from Microsoft for this, so the following can change with the next version of Windows. The first we have to do is capturing the mouse. This is done by SetCapture(HWindow); where HWindow is the handle of the window that should receive the mouse messages. Now we will receive WM_MouseMove messages every time the mouse is moved even if the mouse is not in your client window. If the mouse was moved, we have to detect if the window at the point of the cursor is registered to accept dropped files. So we have to process WM_MOUSEMOVE messages: 1) You can get the current position of the cursor when the message was sent by: Point.X := LoWord(GetMessagePos); Point.Y := HiWord(GetMessagePos); 2) Now we can get the window at this Point: DragWnd := WindowFromPoint(Point); 3) To check if the DragWnd is registered to accept dropped files we have to check if the window has the exStyle ws_Ex_AcceptFiles. If the Style is set, we will set the cursor to a cross, if not we will set the standard cursor: if GetWindowLong(DragWnd,gwl_ExStyle) and ws_Ex_AcceptFiles = ws_Ex_AcceptFiles then SetCursor(LoadCursor(0,idc_Cross)) else SetCursor(LoadCursor(0,idc_Arrow)); ws_Ex_AcceptFiles has a value of $00000010; Now if the mouse button is released we have to release the capture and set the default cursor with ReleaseCapture; SetCursor(LoadCursor(0,idc_Arrow)); Get the DropWnd under the cursor (see above) and if the window under the mouse is a D&D window (see above) we have to post a wm_DropFiles message to it. But in this message we have to set wParam to a handle with a Drag&Drop structure and the format of this structure is not documented. I found out that the structure looks like this: type PDragDropStruct =^TDragDropStruct; TDragDropStruct = record DataOffset : word; DropPoint : TPoint; DropInNonClient : bool; Data : array[0..0] of char; end; The meanings of the parts of this structure are: 1) DataOffset : This is the offset where the filenames begin. 2) DropPoint This is the point where the mouse was released. The coordinates are client coordinates. 3) DropInNonClient This flag is set if the mouse was released in the non-client area of the window (e.g., title bar). 4) Data Here is the beginning of the data. All filenames are separated by a chr(0) and the end of this list is terminated by another chr(0). Now we have to do the following: 1) Allocate memory for the Drag&Drop structure 2) Lock the memory 3) Set the data 4) Unlock the memory 5) Use the Handle to the memory as wParam For example we want to have 3 files in the Drag&Drop structure const s : array[1..3,0..255] of char=('C:\autoexec.bat', 'C:\config.sys', 'C:\dos\command.com'); We have to compute the length of all the strings l := 0; for i := 1 to 3 do l := l+ StrLen(s[i]); and have to allocate memory for the whole structure. The memory must be of the type gmem_DDEShare. DataHandle := GlobalAlloc(gmem_DDEShare,sizeof(TDragDropStruct)+l); To set the data we lock the memory DataPtr := GlobalLock(DataHandle); and fill the fields of the structure: with PDragDropStruct(DataPtr)^ do begin DataOffset:=Data-DataPtr; DropInNonClient:=( DefWindowProc(DropWnd,wm_NCHitTest,0,longint(Point))<>htClient); ScreenToClient(DropWnd,Point); DropPoint :=Point; end; where DefWindowProc(DropWnd,wm_NCHitTest,0,longint(Point))<>htClient will test if the cursor is not in the client area of the window. The last data we have to set are the filenames. We do this with a pointer p. Initially we set p to the field data of the structure. p:=PDragDropStruct(DataPtr)^.Data; Now we copy each string to p and set p at the end of the string plus one (StrLen will not compute the chr(0)). for i:=1 to 3 do begin StrCopy(p,s[i]); p:=p+StrLen(s[i])+1; end; If we have copied all strings we have to terminate the list with a chr(0) p^:=#0; At the end we have to unlock the memory with GlobalUnlock(DataHandle); and post the wm_DropFiles message to the DropWnd: PostMessage(DropWnd,wm_DropFiles,DataHandle,0); The file D&DSER.PAS is a simple Drag&Drop server. Just press the left mouse button in the client area and move it to the window of a Drag&Drop client (e.g D&DCLI, see below) and release the button. While moving the mouse the cursor will change to a cross, if the window under the cursor is a Drag&Drop client. D&DCLI.PAS is an implementation of a simple Drag&Drop client. It will print out the dropped files in a WinCrt window. It is nearly the same code as the trash can in the last issue. C++ Beginner's Column By Mike Wallace I promised in the last issue I would start a beginner's column on C++, and I didn't want to lie to you. I have been reading some books lately on C++ because I don't know anything about it. My plan is to learn enough to write a "Hello world" program in C++ for Windows (for starters), gradually move on to more advanced programs and try to share with you everything I learn along the way. Hope this suits you because it's the best way I can think of to approach this. If you have any suggestions/complaints please don't hesitate to let me know. We have a ways to go before we can start coding so be patient for now. This stuff can be a little bizarre, so I'll try to explain it as best I can. I've been programming for about 10 years and have become pretty comfortable with Pascal, C, COBOL, FORTRAN, BASIC and the rest of the most popular languages, and I've noticed they have a lot of similarities when it comes to program structure, data abstraction, etc. If you already know how to program and now want to learn C++, forget everything you've learned. If you've never programmed before, then you're a step ahead of the rest of us. C++ is unlike anything I've seen - it's a whole new approach to programming. The best book I've seen on the subject is Borland's excellent "Object-Oriented Programming Guide" that came with Turbo Pascal 5.5. I hope this guide is still packaged with the latest version of TP - chapter one is a great read and helped clear up some of the more mystifying aspects of OOP for me. OOP is something like the sound of one hand clapping - if you don't think about it too hard and ignore what you already know, it's easy to imagine. Consider an apple. In abstract terms, it's just an object, and like any object, it is described by its physical properties (an apple has to have weight, for example). Think of these properties as functions of the object. It is important to keep in mind that we're not talking about a specific apple, but any apple that ever existed. The function describing, say, the weight, stays the same - only the values going into the function differ. So, the nature of an apple is intertwined with the functions describing it - they can be encapsulated to form an object. This is an important concept in OOP. "Encapsulation" refers to combining a record with the code that manipulates it to form an object. Let's move on to an example closer related to programming: drawing graphics. If we wanted to write a program for drawing lines, circles, etc., we could start with defining an object called Point, which has as three fields: x coordinate (integer), y coordinate (integer) and on/off (boolean). The first two fields give the location of Point and the third says if it is displayed (on) or not (off). If we wanted to extend our program to include circles, we could a define a new object called Circle that had the same fields as Point plus a field for the radius (x and y would be the center of the circle). To save time, we could declare Circle as an object of type Point with another field for the radius. Circle is a descendent of Point because Circle has inherited the properties of Point. This is another concept important to understanding OOP. "Inheritance" refers to defining an object and using it as a basis for defining descendent objects, with each descendent inheriting access to all its ancestors' code and data. Finally, let's say you want to write a routine for drawing a Point and another for a Circle. Although the two routines would be implemented differently, they're both doing nothing more than showing an object. In C++, it is possible to declare two routines with the same name, and here we could do it with the routine "Show." The difference between the two is that one would be tied to the object Point and the other would be tied to Circle. This is called "polymorphism", and is giving an action (here, showing an arbitrary object) a single name (e.g., "Show") that is used by different objects in the hierarchy , with each object implementing the action in a manner suited to that object. Well, that's all for this month. I've stayed away from code here because it's too early to start with it. C++ is too different than, say, C, to jump into code, I think [that's his way of saying he hasn't gotten that far either - Pete]. I hope this makes sense so far. If you're thinking, "I never did this with Pascal", then you're starting to get the point. It's not just a computer language - it's a way of programming that reflects the way we think, much more so than C, for example. Don't think too hard about it and it should make more sense than what you already know about programming. If none of this makes sense, drop me a line and I'll see what I can do. Beginner's Column By Mike Wallace and Pete Davis Last month we covered the .DEF file and started on resources by describing dialog boxes. This month we'll finish the .RC file by explaining menus, and then begin on the C code for our "Hello World" program. A Windows program isn't a Windows program without a menu. It's probably the first thing you look for when you're running a Windows app. If you've wondered how to create one yourself, you've come to the right place. You describe your menu in the .RC file using the following format: MENU BEGIN POPUP "", BEGIN MENUITEM "", [, options] END MENUITEM "", [, options] END You can have as many menu items as you want (or will fit on the menu bar), and even have popup menus inside other popup menus if you want. I kept the format simple so it wouldn't be overwhelming. Here's the explanation for the codes: anything inside "<>" is required, and anything inside "[]" is optional. You have to give your menu a name. You can put the POPUPs and MENUITEMs in any order you want, but don't forget the BEGIN/END statements that go right after the POPUP statement. Any MENUITEM inside the BEGIN/END appears under that popup menu. MENUITEMs that appear outside the BEGIN/END of a popup menu appear on the menu bar, but they won't have a menu under them. For each menu item, you should give it a variable (defined in a .H file included in the .RC file) that you'll use when your program checks for which menu item was selected by the user. For any menu item, you can include the following options: CHECKED - The menu item has a check next to it GRAYED - The menu item text is grayed and inactive HELP - The item has a vertical line to the left. You can also include a "\a" at the beginning of the text if you want to item to appear on the menu bar's far right side. INACTIVE - The menu item name is displayed but cannot be activated. MENUBARBREAK - When used on a menu, the item is placed on a new line. On popups, the item is placed in a new column. A line separates this item from the previous one. MENUBREAK - Same as MENUBARBREAK, except for popup menus : no dividing line. A couple of more hints: If you want to make a character in a menu item name underscored (to allow for ALT-whatever), add an "&" before the character in the name. Also, you can add the line "MENUITEM SEPARATOR" to add a horizontal line between popup menu bar items. We're now ready to write the .RC file for "Hello World". Here it is: /* hello.rc */ #include #include amenu MENU BEGIN POPUP "&Text" BEGIN MENUITEM "&Write", IDM_WRITE END MENUITEM "&Quit", IDM_QUIT MENUITEM "\a&Help", IDM_HELP, INACTIVE END Here's "hello.h": /* hello.h */ /* define menu bar items */ #define IDM_WRITE 1 #define IDM_QUIT 5 #define IDM_HELP 10 /* function prototypes */ Long FAR PASCAL MainWndProc (HWND, unsigned, WORD, LONG); The "windows.h" file is included with the Microsoft Windows SDK, and is required for any Windows program. The above two files will change as our program grows, but it should suit our needs for now. We'll describe any changes we make to any file we've already created. Here's Pete to tell you about the C code. Ok, so I guess it's my turn to talk about the coding of this thing. This is a real bread and water Windows program, but it's got all the basics that you're going to find in all Windows programs, and that's what you need at this point. In a normal C program you have your main() function which is the first function to get called in a C program. In Windows, instead of main(), you have WinMain(). (Real original, eh?) The WinMain function is where you take care of all the initialization. The main things you want to do is register your window class. Now, this makes it sound like object oriented stuff all of a sudden, and I suppose it is, but all you're really doing is telling Windows a few things about your window. (You have to keep in mind that Windows is essentially an object oriented operating system. Although our programming isn't object oriented, per se, we are emulating an object oriented programming environment.) Instead of showing the Window Class structure here, I'll just discuss it. I've labeled the members of the structure in the code, so you'll be able to relate what I write here to the structure members in the code. The hCursor is basically done by a load cursor. Most applications jut choose the generic IDC_ARROW cursor. The hIcon is where you really get to have fun. This is the icon that is shown when your application is minimized. The hInstance is simply the instance for this application. The hbrBackground is the class of the background brush, meaning, basically, the color or pattern used in your window. This is usually white. lpszMenuName is a pointer to a null-terminated string which has the resource name of your menu. style has several options, most of which are a little more than we need to go into at this point. My suggestion is use CD_HREDRAW | CS_VREDRAW. This basically means to redraw the window if the horizontal or vertical window sizes change. The cbClsExtra and cbWndExtra are a bit more complex than we should be going into at this point, so like so many other things, we'll hold them off for another day. Just keep them NULL for now. After registering your window class, you have to initialize the instance. What this means is that in a Windows program, you can run the same program several times at once (not all Windows applications, but ones that are written to allow it.) Each copy of the program running is called an instance. The reason you have to initialize the instance is because, each one of those versions is going to share the same copy of code. (You don't have to share the code, but it's bad practice not to, so we're not going to say any more about that no no.) Also, because handling multiple instances of an application can be a bit tricky for the beginner, we're going to set up our code to reject attempts to run multiple instances. At the end of all of this you set up your message queue. This is fairly simple to do and fairly generic. Most programs do this the same way. There are reasons for changing it, sometimes, but we're not going to get into that either, at this point. When you initialize the instance, you also tell Windows what the procedure is that is going to handle the messages for your main Window. Here's where the multi-tasking of Windows takes place. See, what Windows does here is that every time something happens in your window, Windows calls this procedure and passes along a message describing what happened. This could be something like a mouse movement, a menu selection, etc... See, you never explicitly call the procedure that handles your main window, what you do is tell Windows where the procedure is and it will know when to call it. The best way to handle your main window procedure is to setup a switch/case structure where each case of your switch statement handles a different message. example: /* Switch structure for message */ switch(message) { case WM_CREATE: .... .... .... break; case WM_Another_Message: .... .... .... break; etc... Windows messages generally start with WM_. The WM meaning Windows Message, oddly enough. After each case would be the code to handle whatever that action requires. For example, the WM_CREATE message is called right before a window (or dialog box for that matter) is created, so it's a good candidate for initializing variables and that kind of stuff that you want to do every time your window is created. A little side note here: There are two types of dialog boxes, Modal and Modeless (Yes, one is al and the other is el). The Modal dialog boxes are very similar to windows in that they are given a procedure that handles messages that are passed to them. (We'll discuss Modal dialog boxes at a later time also. One thing at a time.) I digress... Anyway, the break after each case statement is just a quick way out of the switch/case structure. It basically means that you're done handling that particular message. Ok, so what are we learning here? Well, Windows programs aren't linear in the sense that you run a procedure which may run another procedure and so on. Windows is event driven which means that it reacts to events that take place. When you move the mouse, that's an event, so your procedure gets called. When you hit a mouse button, that's another event, but the same procedure gets called. This procedure should respond differently depending on the message it receives. That should be all you need. I've included the Hello World code and I've put as much inline documentation as will make sense. I suggest you give it a whirl, and like any of our articles, if you have questions, let us know. If we've inadvertently skipped something, send us a message and we'll amend our goofs. Next month you'll have Dave Campbell coming to you directly from Arizona. He's a hell of a programmer, so he'll have lots to show you and hopefully, give you a little different perspective than Mike and I, that way if you don't understand what we're saying, maybe you'll understand it when Dave says it. So, so long and thanks for reading. Hope you'll treat Dave as well as you've treated us. Using LZExpand Library Alex Fedorov LZEXPAND.DLL is supplied with Windows to allow programmers to unpack files, previously packed with COMPRESS.EXE utility. Here we will look at the main functions of this library. Note, that you can use EXPAND.EXE utility to unpack files instead of using functions from LZEXPAND library. Mostly, this library is used for installation programs. (This article is intended for TPW programmers). Data compression Data compression is a task to lower the file size by converting the repeated data with some other sequences. In text files such repeated data can be spaces, mostly used chars or even the whole strings. Due the compression such repeated sequences are replaced with the shorter one. Several data compression algorithms exist. One of the most popular is the Huffman algorithm, based on the frequencies of chars repetitions in text. Another one is a run-length encoding algorithm, where the repeated chars are replaced by pairs: the first nibble contains the count of repetitions and the second one the char code itself. Also, the Lempel-Ziv algorithm is widely used and the COMPRESS.EXE utility is based on it. Data expansion Applications use functions from LZEXPAND.DLL to unpack files, compressed with COMPRESS.EXE utility. Here is the actions flow to expand one or more files. Unpacking one file: ~ Open a compressed file with LZOpenFile. Also, the file can be opened with OpenFile or LZInit functions. ~ Open a destination file using LZOpenFile or OpenFile functions. ~ Use LZCopy to copy data from source to destination using file handles from LZOpenFile or LZInit. ~ Close both files with LZClose. Unpacking several files: To unpack several files you perform the following actions: ~ Open source file with LZOpenFile or with OpenFile and LZInit functions. ~ Open destination file using LZOpenFile or OpenFile functions. ~ Allocate memory for copy operations with LZStart function. ~ Use CopyLZFile to copy source file to destination file. ~ Free allocated memory with LZDone function. ~ Close all files with LZClose function. Reading data from compressed files Instead of unpacking the whole file, an application can unpack file piece-by-piece, using LZSeek and LZRead functions. These functions can be useful when we unpack the huge files. To use the functions from LZEXPAND.DLL you need the unit LZEXPAND.TPU which is supplied with Turbo Pascal for Windows 1.5 or with Borland Pascal for Windows. Here is the example of how to use LZEXPAND.DLL functions { LZDEMO - The demo of LZEXPAND.DLL functions usage } uses LZExpand,WinTypes,WinProcs; Const szSrc = 'MYFILE.PAK'; {Packed file name} Var szFileName : Array[0..127] of Char; ofStrSrc : TOfStruct; ofStrDest : TOfStruct; hSrcFile : THandle; hDstFile : THandle; Total : LongInt; Begin {Open compressed file} hSrcFile := LZOpenFile(szSrc,OfStrSrc,of_Read); {Get the original name of file} GetExpandedName(szSrc,szFileName); {Create the file with szFileName} hDstFile := LZOpenFile(szFileName,ofStrDest,of_Create); {Unpack file while copying it} Total := LZCopy(hSrcFile,hDstFile); {LZCopy returns the number of bytes written} {Close both files} LZClose(hSrcFile); LZClose(hDstFile); End. Note: In the example above, we used the LZOpenFile function, which automatically calls LZInit function, which performs some initialization. The result code of this function can tell us whether or not the file was compressed with COMPRESS.EXE. To do this, you need to open the file with the OpenFile function (from KERNEL) and then call LZInit directly, giving it the file handle from OpenFile. LZInit returns the file handle. If the value for this handle is not the same as the argument, the file was compressed and we can unpack it. Here is the example: hSrcFile := OpenFile(szSrc,ofStrSrc,of_Read); hCompFile := LZInit(hSrcFile); If hCompFile <> hSrcFile then {File was compressed with COMPRESS.EXE} Else If hCompFile = hSrcFile then {File was not compressed} Else {Some error encountered; must check LZError_XXX codes} Also note, the GetExpandedName function returns the original name only when the file was compressed by COMPRES.EXE with /r option. Alex is a freelance programmer and the editor for "Computer Press" magazine. Alex lives in Moscow, Russia. Implementing A Linked List - Revisited By Mike Wallace I didn't expect this to happen. I have caused a small uproar by last month's article "Implementing a Linked List Using the Global Heap." Several people had differing opinions on the best way to do this, and two of them have promised articles showing two different ways to do this, which we hope to publish soon. Part of it was my fault: at the end of the article, I intended to mention that it was very easy to alter the program to use the local heap (instead of the global heap) - all you need to do is change the Global commands (e.g., GlobalAlloc) to Local commands (e.g., LocalAlloc) and the FAR pointers to NEAR. I arbitrarily chose the global heap for the program, and several people pointed out (and for good reasons) that it's always better to use the local heap if at all possible. Hope I haven't confused anybody. In case anyone is interested in using the method I proposed last month, then I have bad news: there was a bug in last month's code involving freeing the memory blocks. I have included a revised version of the program with this issue. An Introductory Look at DLLs and The Use of Make Files By Rod Haxton Whenever one begins learning Windows he/she always seems to hear talk about DLLs (Dynamic Link Libraries). Learning Windows is a very long process. Like anything in the programming world there's lots to learn, and not that much time to learn it in. Thus, many beginning Windows programmers bypass Petzold's chapter on DLLs until a later time. This article may not be of great help in understanding DLLs for Windows programmers just getting their feet wet. But, if you have been writing code for Windows and feel competent, and have not yet tackled DLLs, this code may give you a little understanding of how DLLs work. Also, if you have some understanding of 80x86 assembly language underneath-the-hood workings, this will be useful in understanding how DLLs work in Windows. A bonus to this article is that it demonstrates how a large application or for that matter any size app should be laid out. The programming example accompanying this article shows how source code should be broken up into seperate modules. The demo also makes use of Make files and shows how they can help speed the development of projects, and also demonstrates how to break code up into multiple code segments. So often in Windows books the programs demonstrated are built as one application with one code and data segment. This isn't really what we as developers need. In learning DLLs there are a few areas that one should focus on to gain 'THE KNOWLEDGE'; this conjures up images of some monks sitting around reading thick books. Cutting and pasting code samples from on-line articles or typing them in will get you something working but, if you do not understand what is really going on in Windows you will be held hostage to some unknown events and also your own imagination, or the lack thereof. Thus, there are some things to focus on when learning about DLLs. They are: * DS != SS * Windows & DLLs entry/exit code * Exports & Imports * LibEntry & LibMain * WEP function DS != SS -------- In a regular Windows application program the Stack and Data Segment are the same. If you set up your program to have multiple code segments, the Code segments can change during Windows function calls, far calls, and return from far calls. The changing of the code segments takes place because most code segments are flagged as moveable and discardable. If the code segment has been flagged as a fixed segment then the code segment will stay put. But, in understanding DLLs you need to concentrate on DS and SS. As I mentioned, in regular Windows apps DS == SS. Standard C routines can be used in non-DLL apps. They do not assume DS == SS, and generally reference pointer variables relative to DS. Remember, when functions are called a stack frame is created, which holds the passed variables, the returning CS:IP address, and space is created to hold any local variables to the routine. Thus, references to your passed-in near pointer variables are seen by the stack, because C does not make a distinction between stack segment-based variables and data segment-based variables. DLLs on the other hand have their own data segments. This is one of the reasons that makes them appealing to those developers writing big applications. When a routine to a DLL is called, the calling function's, if its not in the DLL, DS and CS are placed on the stack. The DLL uses the stack of the calling function. The DLL's DS and CS are given scope. Thus, the calling app (if passing any variables to the DLL) must pass them as far pointers in order for the variables to have scope. Also, standard C routines cannot be used, because DS != SS, and the standard library routines reference the variables from DS. If you do call a standard C routine or another DLL's function with near pointers, when the function returns the variables will be unchanged. Ways to get around this problem are to declare your local variables in your DLL as static or pass them as far pointers. APP DLL --------------- --------------- | | | | | CS | | CS | --------------- --------------- DS DS SHARED SS------------------------------------------------SS A detailed discussion of DS != SS can be found in Charles Petzold's 'Programming Windows', and Mike Klein's 'DLLs And Memory Management'. A step to really comprehending DLLs is to understand DS != SS. Windows & DLLs Entry/Exit Code ------------------------------ Another aspect of understanding DLLs is to understand how Windows loads application functions and DLLs. Again, here a little understanding of how a MS-DOS C compiler handles function loading and assembly language can be helpful. Now, if you do not happen to be a low-level system person and/or have no desire to be, you may be asking yourself why this is important. Well, this really isn't important if you don't mind writing code and not understanding what's really taking place. But remember, you will be at a real disadvantage when a bug crops up and you go into your debugger to find out what is wrong and you have no idea of what you're looking for. Under DOS the compiler sets up segments as the following: mov ax, _DATA mov ds, ax And, function entry/exit code saves registers and makes space on the stack for passed variables and local variables, push bp mov bp, sp sub sp, . . . mov sp, bp pop bp ret ;This can be a near or far return. Also, if ;the function was called with the PASCAL ;directive the number of parameters passed ;to the stack needs to be added to the ;return. Windows functions are all set up to be far calls. This provides the built-in ability to swap and discard code segments in and out of memory. The entry exit code for the non-exported far functions looks like the following: push ds -----------------------| same as: pop ax -----------------------| mov ax, ds nop inc bp push bp mov bp, sp push ds ---------------------- save ds again? mov ds, ax ---------------------- here again ? sub sp, . . . dec bp dec bp mov sp, bp pop ds pop bp dec bp ret Looking at the Windows entry/exit code above we see that it is really similar to the DOS version. It just does extra work. The reason being is that Windows modifies the entry code when your exported function is called. Windows replaces the first two instructions ('push ds' and 'pop ax') with NOPs. If you noticed the code above where I have stated 'here again?', the instruction is 'mov ds, ax'. But, now ax contains nothing. Before it held the value of ds. What happens is that during LINK Windows places into EXE file a table that lists the references to your far calls and Windows functions. Thus, when Windows runs and calls your far function the register ax has already been loaded with the segment location. Exported functions DS are handled during the registering of window class structures created or with the MakeProcInstance for call-back functions. The MakeProcInstance tells Windows to do some 'thunking'. Thunking handles the setting up of code and data segments. Each instance of a Windows function will have its own DS, but all instances will refer to one CS. Far calls and Window functions entry code: nop nop nop inc bp push bp mov bp, sp push ds mov ds, ax---------------->'Instance Thunk' located in fixed memory position. mov ax, sub sp, DLLs entry code is as simple as the DOS version. Because DLLs have their own DS and there can only be one instance of a DLL there's no need to determine the DLL's DS at run time or create an instance thunk. The DS is already known so it can be set. mov ax, inc bp push bp mov bp, sp push ds mov ds, ax Exports and Imports ------------------- You should already know about Exported functions if you have written Windows code. But, here's a brief tutorial. All Windows functions that receive messages must be exported. The export tells the compiler that references to the exported functions will be resolved at run-time. Imported functions are also used to resolve function references. The IMPORTS statement in a DEF file tells the Linker that the functions are defined elsewhere and relocation info will be resolved at run-time. Imported functions can be handled in two ways. One is to list them in a DEF file under the IMPORTS statement. The other is to place them in an import library and link them into your code. The latter is the approach that I use because it reduces the headache of updating DEF files during development. Also, listing the import functions in DEF files requires a lot of memorization when you start dealing with multiple DLLs. Later, I will show how I handle multiple DLLs and imports. LibEntry and LibMain -------------------- Every Windows executable modules needs an entry point. When writing apps for Windows this code is hidden from the developer. However, when writing DLLs the source to the library entry routine, LibEntry(), is provided to you by the SDK. Usually, you do not have to touch this code - the SDK provides 'libentry' for you to link into your DLL. 'libentry' is the object file for LibEntry(). If you are creating a resource-only DLL you do have to modify LibEntry() a little bit. LibEntry() also calls a function LibMain() that you must supply. When the application program starts up all DLLs tied to that app get called. The initial call for the DLL is the only guaranteed time that the library will be called. LibEntry() initializes the DLL's local heap by making a call to LocalInit(). Under Windows 3.0, due to Real Mode support, LocalInit() locks the DLL's data segment; the locking of the data segment does not take place under Windows 3.1. On exit from LocalInit(), LibEntry() calls LibMain(). If the data segment was locked your LibMain() function must unlock it. LibEntry() and LibMain() initialize your DLL and are only called once. You will see in my Make and DEF files that I place LibMain() in an initialization code segment. This way the segment will be flagged after it is used to be discarded and when memory becomes an issue it will be dumped by Windows. WEP Function -------------- The WEP() routine is another function that your DLL is required to have. The SDK says that this function gets called once, when a DLL is about to be unloaded. This does not seem to be the case under Windows 3.0. When I have watched it under Codeview, I have never seen it called. WEP() has an input parameter that identifies whether the exit is being caused by the system or the application. Whatever the reason for the exit WEP() should return 1. You will notice in my examples that I do not do any testing for the type of exit: I just return 1. WEP()'s name is made resident in the DLL's name table, thus it is always in memory and never gets swapped out. Programming Example ------------------- Now that the basics of DLLs have been discussed. I'll focus the rest of this article on a sample DLL app that I've written that demonstrates DLLs, the use of Near/Far function calls, Near/Far pointers, calling a Dialog Box function located in a DLL, how to create an import library, the use of Make files, and how to create multiple code segments. The following DLL example is called RODSAPP (how appropriate). It's comprised of an application program and two DLLs. The app files are rodsapp.c, winmain.c, and wndproc.c. The file rodsapp.c contains code for creating and initializing the Windows application. The file winmain.c handles the Windows message dispatcher. The wndproc.c module is a standard Windows function that has a menu bar. The menu bar is the entry into the DLLs. The menu items are About, One and Two. Clicking on the About option demonstrates a dialog box whose function resides inside a DLL (lib2a.c). Clicking on menu option One causes a call to the testlib.dll (testlib.c). The function StepOne inside testlib.dll demonstrates variables that are referenced from DS and SS. I've provided total explanations inside the source so that understanding would be easier. Clicking on menu option Two also makes a call to testlib.dll that in turn calls the DLL lib2.c. The calls inside these DLLs also demonstrates what happens to variables that are referenced from DS and SS. The Make file used with the program compiles with the Codeview debugger options set. I suggest that you run the program under Codeview a few times and keep an eye on the DS and SS registers. Also, watch the variables inside routines and note their addresses and watch what happens to those variables addresses when you step into a routine inside a DLL. Along with the source files is a batch program DOALL.BAT. It runs all the Make files and dumps any warning or error messages out to a file named peek. Also, the Make file LIB.MAK creates an import library 'test.lib'. This Make file should be run first in order to create the import library that will be linked into the DLLs. Changes to the DEF files will re-make LIB.MAK but the *.dll files must be deleted and re-linked with the new import library. Make File Explanation --------------------- The following is a snippet of the rodsapp.mak Make file; note that the following statements that begin with '*' do not appear in the actual Make file. TMPDRIVE=c:\tmp WLIB=libw mlibcew *sets up a macro for the libraries that *will be used during linking IMPLIBS=test *the DLLs import library (see next *section for how this library was *created). * object code of the modules used in RODSAPP.EXE OBJ=rodsapp.obj winmain.obj wndproc.obj * compiler option flags; a detailed meaning found in source CFLAGS=cl /c /AM /D LINT_ARGS /Gws /W3 /Zip /Od /nologo # Update the executable file if necessary, and if so, add the # resource back in. The /NOE must be included when linking with # Windows libraries. rodsapp.exe: $(OBJ) rodsapp.res rodsapp.def del $(TMPDRIVE)lk.res echo $(OBJ)>>$(TMPDRIVE)lk.res echo rodsapp.exe/align:16>>$(TMPDRIVE)lk.res echo rodsapp.map/map/NOD/CO/LI>>$(TMPDRIVE)lk.res echo $(WLIB) $(IMPLIBS)>>$(TMPDRIVE)lk.res echo rodsapp.def>>$(TMPDRIVE)lk.res link @$(TMPDRIVE)lk.res rc rodsapp.res # Update the object file if necessary * The following demonstrates how to seperate your code into code * segments. The -NT allows one to name the code segment. This * name is then placed in the modules DEF file under the SEGMENTS * statement and given the control flags that you wish the code * segment to follow. rodsapp.obj: rodsapp.c rodsapp.h $(CC) $(CFLAGS) -NT Initialization_code rodsapp.c winmain.obj: winmain.c rodsapp.h $(CC) $(CFLAGS) -NT Resident_code winmain.c wndproc.obj: wndproc.c rodsapp.h testlib.h $(CC) $(CFLAGS) -NT Resident_code wndproc.c # Update the resource if necessary rodsapp.res: rodsapp.rc rodsapp.h rc -r rodsapp.rc DEF File Differences --------------------- The differences between a Windows application definitions file and a DLL's is that in the Windows app DEF file there is a NAME statement that defines the app's name. A DLL has a LIBRARY statement to do the same thing. A DLL does not need a STUB statement since it cannot be launched into execution without an application calling it. A DLL also has no STACK statement. Since the DLL uses the SS of the calling application it needs not define a stack size. Conclusion ----------- Things to consider when developing applications and using DLLs. DLLs are not magical elements to be used for everything. If you can do something fast and it's only going to be used by one application, that is the code will never really be used in some future app, stay away from DLLs. DLLs are slow. It takes numerous cycles to load DLLs, segment adjustments must be made. The DLL far call means that the code and data segments must be loaded. Hopefully this article will help you in your understanding of DLLs. I hope that the code samples are clear enough to help you. I tried not to give too much code, which can be overwhelming when you are trying to learn something, but just enough to represent the makeup of real projects. There are other topics under DLLs that I did not cover like User Resource DLLs and the handling of global data across DLLs. Maybe someone else will provide an article on User Resource DLLs in a future article. I plan to submit for the next issue of WPJ an article that demonstrates a way I figured out to handle global variables across DLLs. Good luck! Rod Haxton can be contacted @ (703) 883-0226 or (202) 269-3780. The Windows Help Magician By Jim Youngman Since I do not program for the sake of programming, I am always looking for shortcuts. The Windows Help Magician has proven to be one of the best. I think it was written in Visual Basic, but don't let that put you off it. It's a great little application and a real time saver. The files it produces can be compiled with Microsoft or Borland Help Compilers for Windows 3.0 or 3.1. I am writing my first fully fledged Windows program. It is to be used mainly by computer illiterate people and so a good Help file will be essential. When I started investigating how to prepare such files I was overwhelmed by the prospect: all those control characters that have to be inserted! The Windows Help Magician eliminates all of that. Using a well designed interface, the program does all of the hard work. All I need to do is think out the design of the Help system. The friendly Magician gives me a great set of tools for creating the index, browse groups, hypertext jumps, popups and keywords. You can also include bitmaps in your help files. So far I have only played about a bit with the product. I am looking forward to exploring it further with the real job. The Windows Help Magician is available from the publishers: Software Interphase Incorporated 82 Cucumber Hill Road, Foster, RI 02825, USA FAX (401) 397-6814 BBS (401) 397-4601 There is a fully operational demo disk which is limited to help files of 7 pages. The full version allows 200 pages. The Last Page By Mike Wallace This is too cool. Pete and I hoped a couple of hundred people might pick up the first issue of WPJ (Official motto: "It's not just for breakfast anymore"). After three weeks on various bulletin boards, we counted over a thousand, and that doesn't even include Internet (we have no way of tracking the number of downloads there). We've gotten mail and articles from such faraway places as Russia, Germany, South America and central Jersey. We hope the number of downloads goes up every month; if it starts to go down, we're in trouble. From the mail we've received, people seem to like it and want to help out. It's been overwhelming. If you're thinking of writing an article, don't be shy. Others are doing it. You don't have to be a professional writer (we sure aren't), and we can always use more articles. If you don't feel up to writing an article, send us a message and let us know what you think about the magazine. Does it have the putrid stench of rotting eggs? Has it helped your Windows programming? Inquiring minds want to know. Remember: this is your magazine as much as our's. The only thing I'm disappointed about is I haven't gotten any mail for this column. I really hope you people don't make me write this whole page by myself! As much as I wish I could fill this page with poetic prose every month, that just ain't gonna happen. What's on your mind? The only programmers I know all live in Washington, D.C. (Official driving slogan: "Death before yielding"), and here everyone is a government contractor, and there's only so much you can talk about with a contractor before things get really dull. What are programmers doing in, say, California? Texas? Europe? I know you people have something on your mind and you're thinking, "If only there was some sort of internationally distributed electronic forum for sharing my most personal thoughts with people who will write about me in their local newspapers and ban me from ever visiting their country..." Well, wonder no more, because now you have your chance. You can get your 15 minutes of fame simply by writing anything (and I really mean ANYTHING [He means within reason -Pete]) that both piques my interest and, most importantly, means I don't have to write as much. What could be simpler? While I'm thinking about it, I want to thank the hard-working sysops on CompuServe, and especially in the WINSDK forum. I've flooded these people with questions lately and they've been great, and I really hope they don't mind if I use their answers in future WPJ articles (ha! ha! just kidding!). Seriously, these people really know their stuff and will answer any question, no matter how inane (take it from someone who's asked more than one inane question). An "Honorable Mention" goes to sysop Tammy Steele for her wealth of knowledge and great attitude. Stop by the WPJ offices anytime to pick up your official WPJ t-shirt, Tammy. I'm out of room and out of time. Hope to hear from ya'll soon. Getting in touch with us: Right now there are only four ways to get in touch with us. If you have access to the Internet or BITNET, you can get in touch with us at: HJ647C at GWUVM.GWU.EDU -or- HJ647C at GWUVM.BITNET (Pete) GEnie: P.DAVIS5 (Pete) CompuServe: 71141,2071 (Mike) WPJ BBS (703) 503-3021 (Mike and Pete) You can also send paper mail to: Windows Programmer's Journal 9436 Mirror Pond Drive Fairfax, VA 22032 U.S.A. As soon as we replace the hard drive, the Windows Programmer's Journal BBS can be reached at: (703) 503-3021. You can get in touch with either Mike or Pete at this number. The BBS is going to be relatively new, so don't be surprised if it's a little buggy in the beginning. Right now it's only 2400 baud as my 9600 baud modem died on me, soon you can expect it to go up to 14,400. You can also expect to see a Fido-net node address in the near future.. In future issues we will be posting addresses of contributors and columnists who don't mind you knowing their addresses. We will also contact any writers from the first two issues and see if they want their mail addresses made available for you to respond to them. For now, send your comments to us and we'll forward them.